﻿using System;
using System.Linq;
using System.Text;

namespace TestWebApplication.CommerceTestDataProvider
{
    /// <summary>
    /// A global random-number generator, which will eliminate the possibility that
    /// multiple sequences will get seeded with the same time value.
    /// </summary>
    public class GlobalRandom
    {
        // The global Random object is used to generate seed only
        private static readonly Random globalRandom = new Random();

        [ThreadStatic]
        private static Random random;

        /// <summary>
        /// A Private Constructor, to prevent initialization of this class.
        /// </summary>
        private GlobalRandom() { }

        /// <summary>
        /// Get the per-thread shared static Random object.
        /// </summary>
        /// <returns>
        /// The per-thread shared static Random object.
        /// </returns>
        private static Random GetRandomObject()
        {
            if (null == random)
            {
                int seed;
                lock (globalRandom)
                {
                    seed = globalRandom.Next();
                }
                random = new Random(seed);
            }
            return random;
        }

        /// <summary>
        /// Returns a random number between 0.0 and 1.0.
        /// </summary>
        /// <returns>
        /// A double-precision floating point number greater than or equal to 0.0, and less than 1.0.
        /// </returns>
        public static double NextDouble()
        {
            return GetRandomObject().NextDouble();
        }

        public static double NextDouble(double min, double max)
        {
            return GetRandomObject().NextDouble() * (max - min) + min;
        }

        /// <summary>
        /// Returns a positive random number.
        /// </summary>
        /// <returns>
        /// A number greater than or equal to 0.
        /// </returns>
        public static int Next()
        {
            return GetRandomObject().Next();
        }

        /// <summary>
        /// Returns a positive random number less than the specified maximum.
        /// </summary>
        /// <param name="maxValue">
        /// The upper bound of the random number to be generated. maxValue must be greater than or equal to zero.
        /// </param>
        /// <returns>
        /// A number greater than or equal to zero, and less than maxValue.
        /// </returns>
        public static int Next(int maxValue)
        {
            return GetRandomObject().Next(maxValue);
        }

        /// <summary>
        /// Returns a random number within a specified range.
        /// </summary>
        /// <param name="minValue">
        /// The lower bound of the random number returned.
        /// </param>
        /// <param name="maxValue">
        /// The upper bound of the random number returned. maxValue must be greater than or equal to minValue.
        /// </param>
        /// <returns>
        /// A number greater than or equal to minValue and less than maxValue. If minValue equals maxValue, minValue is returned.
        /// </returns>
        public static int Next(int minValue, int maxValue)
        {
            return GetRandomObject().Next(minValue, maxValue);
        }

        /// <summary>
        /// Returns a positive random number less than the specified maximum.
        /// </summary>
        /// <param name="maxValue">
        /// The upper bound of the random number to be generated. maxValue must be greater than or equal to zero.
        /// </param>
        /// <returns>
        /// A number greater than or equal to zero, and less than maxValue.
        /// </returns>
        /// <remarks>
        /// If MaxValue is greater than Int32.MaxValue, this method will handle
        /// the work of figuring out the random number for you.
        /// </remarks>
        public static uint Next(uint maxValue)
        {
            if (maxValue > (uint)Int32.MaxValue)
            {
                int extra = (int)(maxValue - (uint)Int32.MaxValue);
                uint part1 = (uint)Next(Int32.MaxValue);
                uint part2 = (uint)Next(extra);
                return part1 + part2;
            }
            else
            {
                return (uint)Next((int)maxValue);
            }
        }

        /// <summary>
        /// Returns a randomly chosen boolean value.
        /// </summary>
        /// <returns>
        /// A randomly chosen boolean value.
        /// </returns>
        public static bool NextBoolean()
        {
            return (Next(2) == 1);
        }

        /// <summary>
        /// Fills the elements of a specified array of bytes with random numbers.
        /// </summary>
        /// <param name="buffer">
        /// An array of bytes to contain random numbers.
        /// </param>
        public static void NextBytes(byte[] buffer)
        {
            GetRandomObject().NextBytes(buffer);
        }

        /// <summary>Creates a random string, containing only alphabetic characters</summary>
        /// <param name="length">The length of the string to create</param>
        /// <returns>The generated string</returns>
        public static string CreateAlphabeticString(int length)
        {
            StringBuilder builder = new StringBuilder(length);
            byte[] bytes = new byte[length];
            NextBytes(bytes);
            for (int i = 0; i < length; ++i)
            {
                int choice = bytes[i] % 52;
                char c = (choice < 26)
                    ? (char)((int)'A' + choice)
                    : (char)((int)'a' + choice - 26);
                builder.Append(c);
            }
            return builder.ToString();
        }

        /// <summary>
        /// Returns a random member from the specified enumeration type.  
        /// </summary>
        /// <typeparam name="T">An instance of the enumeration type.</typeparam>
        /// <returns>A valid random instance of the enumeration.</returns>
        public static T NextEnumMember<T>() where T : struct
        {
            Array vals = Enum.GetValues(typeof(T));
            return (T)vals.GetValue(Next(vals.Length));
        }

        /// <summary>Creates a random string, containing only alphabetic characters</summary>
        /// <param name="minLength">The minimum length of the string to create (inclusive)</param>
        /// <param name="maxLength">The maximum length of the string to create (exclusive)</param>
        /// <returns>The generated string</returns>
        public static string CreateAlphabeticString(uint minLength, uint maxLength)
        {
            int length = Next((int)minLength, (int)maxLength);
            return CreateAlphabeticString(length);
        }

        /// <summary>Creates a random string, containing only numeric digit characters</summary>
        /// <param name="length">The length of the string to create</param>
        /// <returns>The generated string</returns>
        public static string CreateNumericString(int length)
        {
            StringBuilder builder = new StringBuilder(length);
            byte[] bytes = new byte[length];
            NextBytes(bytes);
            for (int i = 0; i < length; ++i)
            {
                char c = (char)('0' + bytes[i] % 10);
                builder.Append(c);
            }
            return builder.ToString();
        }

        /// <summary>Creates a random string, containing both alphabetic and numeric characters</summary>
        /// <param name="length">The length of the string to create</param>
        /// <returns>The generated string</returns>
        public static string CreateAlphaNumericString(int length)
        {
            StringBuilder builder = new StringBuilder(length);
            byte[] bytes = new byte[length];
            NextBytes(bytes);
            for (int i = 0; i < length; ++i)
            {
                int choice = bytes[i] % 62;
                char c;
                if(choice < 26)
                {
                    c = (char)((int)'A' + choice);
                }
                else if (choice < 52)
                {
                    c = (char)((int)'a' + choice - 26);
                }
                else
                {
                    c = (char)((int)'0' + choice - 52);
                }
                builder.Append(c);
            }
            return builder.ToString();
        }

        /// <summary>Creates a random IP Address string in IPv4 format</summary>
        /// <returns>The generated string containing IP Address in IPv4 format</returns>
        public static string CreateIPAddress()
        {
            byte[] bytes = new byte[4];
            NextBytes(bytes);
            return string.Join(".", bytes.Select(b => b.ToString()).ToArray());
        }

        /// <summary>Shuffles an array of objects</summary>
        public static void Shuffle<T>(T[] array)
        {
            if (array == null)
            {
                throw new ArgumentNullException("array");
            }
            int count = array.Length;
            for (int i = 0; i < count; i++)
            {
                int rndIndex = Next(count);
                T tmp = array[i];
                array[i] = array[rndIndex];
                array[rndIndex] = tmp;
            }
        }
    }
}
